#include "animwidget.h"

#include <QMap>
#include <QDir>
#include <QDebug>
#include <QPainter>
#include <QTimerEvent>

AnimWidget::AnimWidget(QWidget *parent)
    : QWidget{parent}
{
    mFrameTime = 0.33;
    mNow = 0;
    mResourceDir = QDir::homePath();
}

void AnimWidget::PrepareOpMap(QMultiMap<double, AnimOp> &ops, double startPos)
{
    // Organize multimap into Map of Vectors, and place spawn to head, delete to tail

    mOpMap.clear();
    mNow = startPos;
    auto keys = ops.keys();

    // Remove redundant keys
    {
        double last = -1;
        for(int i = 0, j = keys.size(); i < j; /**/)
        {
            if(keys[i] == last)
            {
                keys.removeAt(i);
                j--;
            }
            else
            {
                last = keys[i];
                i++;
            }
        }
    }

    // Remove ops before start point
    for(int i = 0, j = keys.size(); i < j; /**/)
    {
        if(keys[i] < startPos)
        {
            keys.removeAt(i);
            j--;
        }
        else
        {
            i++;
        }
    }

    mTaskTimeQueue = keys;

    // If many operations in one time point, spawns must be first and kills must be at last
    // A quick dirty sort should work
    QMultiMap<OpCode, AnimOp> sorter;
    for(auto i : keys)
    {
        sorter.clear();
        auto val = ops.values(i);
        for(auto &j : val)
        {
            sorter.insert(j.Code, j);
        }

        mOpMap[i] = QVector<AnimOp> {};
        auto& currentOps = mOpMap[i];
        for(auto &j : sorter.values())
        {
            currentOps.append(j);
        }
    }
}

void AnimWidget::Clear()
{
    mEntities.clear();
    mNow = 0;
}

void AnimWidget::paintEvent(QPaintEvent *e)
{
    Q_UNUSED(e);
    QPainter p;
    p.begin(this);
    QBrush b(Qt::white);
    b.setStyle(Qt::SolidPattern);
    p.fillRect(0, 0, 1280, 720, b);
    for(auto &i : mEntities)
    {
        p.setOpacity(i.Opacity);
        auto size = i.Img->size() / -2;
        QPointF offset { (double)size.width(), (double)size.height() };
        p.translate(i.Pos);
        p.rotate(i.Rotation);
        p.scale(i.Size.width(), i.Size.height());
        p.drawImage(offset, *i.Img);
        p.resetTransform();
    }
    p.end();
}

void AnimWidget::timerEvent(QTimerEvent *e)
{
    Q_UNUSED(e);
    progressAnimation();
    update();
}

template <typename T> void ExecInterpolator(T& intpltr, double now)
{
    if(intpltr)
    {
        if(intpltr->IsWorking())
            intpltr->Interpolate(now);
        else
        {
            delete intpltr;
            intpltr = nullptr;
        }
    }
}

template <typename T> bool VerifyEmptyInterpolator(T& intpltr, double now, double frameTime)
{
    if(!intpltr)
        return true;

    if(!intpltr->IsWorking())
    {
        delete intpltr;
        intpltr = nullptr;
        return true;
    }

    if(intpltr->EndTime() - now < frameTime * 2) // Room for wiggling
    {
        delete intpltr;
        intpltr = nullptr;
        return true;
    }

    // TODO: Raise error
    return false;
}

void AnimWidget::progressAnimation()
{
    // Determine current time
    mNow += mFrameTime;

    // Find out tasks to do
    while(mTaskTimeQueue.size() && mTaskTimeQueue.first() <= mNow)
    {
        auto taskTime = mTaskTimeQueue.takeFirst();
        auto ops = mOpMap.take(taskTime);
        for(auto &i : ops)
        {
            ExecuteOp(i);
        }
    }

    // Update all existing entites
    for(auto &i : mEntities)
    {
        ExecInterpolator(i.PosInterpolator, mNow);
        ExecInterpolator(i.SizeInterpolator, mNow);
        ExecInterpolator(i.RotationInterpolator, mNow);
        ExecInterpolator(i.OpacityInterpolator, mNow);
    }
}

void AnimWidget::ExecuteOp(AnimOp &op)
{
    switch(op.Code)
    {
    case NewEntity:
    {
        auto img = GetImage(op.Args[0]);
        if(!img)
        {
            return;
        }
        AnimEntity ent;
        ent.Img = img;
        ent.Size = { 1, 1 };
        ent.Rotation = 0.0;
        ent.Opacity = 1.0;
        ent.Pos = op.Anim.Dest.toPointF();
        mEntities[op.ObjName] = std::move(ent);
        break;
    }

    case DeleteEntity:
    {
        QMap<QString, AnimEntity>::Iterator it;
        if((it = mEntities.find(op.ObjName)) != mEntities.end())
            mEntities.erase(it);
        else
            ; // TODO: Raise an error here
        break;
    }

    case NewAnim:
    {
        QMap<QString, AnimEntity>::Iterator ent;
        if((ent = mEntities.find(op.ObjName)) == mEntities.end())
        {
            // TODO: Raise Error
            return;
        }

        switch (op.Anim.Type)
        {
        case AnMove:
        {
            if(!VerifyEmptyInterpolator(ent->PosInterpolator, mNow, mFrameTime)) return;
            if(op.Anim.Period == 0.0)
            {
                ent->Pos = op.Anim.Dest.toPointF();
                break;
            }
            ent->PosInterpolator =
                    new Interpolator<QPointF>(ent->Pos,
                                              ent->Pos,
                                              op.Anim.Dest.toPointF(),
                                              mNow,
                                              op.Anim.Period,
                                              op.Anim.Accel);
            break;
        }

        case AnRotate:
        {
            if(!VerifyEmptyInterpolator(ent->RotationInterpolator, mNow, mFrameTime)) return;
            if(op.Anim.Period == 0.0)
            {
                ent->Rotation = op.Anim.Dest.toDouble();
                break;
            }
            ent->RotationInterpolator =
                    new Interpolator<double>(ent->Rotation,
                                             ent->Rotation,
                                             op.Anim.Dest.toDouble(),
                                             mNow,
                                             op.Anim.Period,
                                             op.Anim.Accel);
            break;
        }

        case AnScale:
        {
            if(!VerifyEmptyInterpolator(ent->SizeInterpolator, mNow, mFrameTime)) return;
            if(op.Anim.Period == 0.0)
            {
                ent->Size = op.Anim.Dest.toSizeF();
                break;
            }
            ent->SizeInterpolator =
                    new Interpolator<QSizeF>(ent->Size,
                                             ent->Size,
                                             op.Anim.Dest.toSizeF(),
                                             mNow,
                                             op.Anim.Period,
                                             op.Anim.Accel);
            break;
        }

        case AnOpacity:
        {
            if(!VerifyEmptyInterpolator(ent->OpacityInterpolator, mNow, mFrameTime)) return;
            if(op.Anim.Period == 0.0)
            {
                ent->Opacity = op.Anim.Dest.toDouble();
                break;
            }
            ent->OpacityInterpolator =
                    new Interpolator<double>(ent->Opacity,
                                             ent->Opacity,
                                             op.Anim.Dest.toDouble(),
                                             mNow,
                                             op.Anim.Period,
                                             op.Anim.Accel);
            break;
        }

        default:
            // TODO: Raise Error
            break;
        }
        break;
    }

    default:
        // TODO: Raise error
        break;
    }
}

QImage *AnimWidget::GetImage(QString name)
{
    decltype(mImageCache.begin()) ret;

    if((ret = mImageCache.find(name)) != mImageCache.end())
        return &*ret;

    QImage img(mResourceDir + name);
    if(img.isNull())
    {
        return nullptr;
    }
    else
    {
        mImageCache[name] = std::move(img);
        return &mImageCache[name];
    }
}

void AnimWidget::SetResourceDir(QString s)
{
    mResourceDir = s;
}
